package com.hsurosy.hsuchain.nft.rpc.facade;

import com.alibaba.fastjson2.JSON;
import com.hsurosy.hsuchain.nft.base.exception.BizException;
import com.hsurosy.hsuchain.nft.base.exception.SystemException;
import com.hsurosy.hsuchain.nft.base.response.BaseResponse;
import com.hsurosy.hsuchain.nft.base.response.ResponseCode;
import com.hsurosy.hsuchain.nft.base.utils.BeanValidator;
import jakarta.validation.ValidationException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * Facade切面
 *
 * @Author Hsu琛君珩
 * @Date 2024-10-05 4:54
 * @Description
 * @Version: v1.0.0
 */
@Aspect
@Component
public class FacadeAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(FacadeAspect.class);

    /**
     * 执行切面方法，进行参数校验和异常处理
     *
     * @param pjp 连接点
     * @return 方法执行结果
     * @throws Exception 发生的异常
     */
    @Around("@annotation(cn.hollis.nft.turbo.rpc.facade.Facade)")
    public Object facade(ProceedingJoinPoint pjp) throws Exception {

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        Object[] args = pjp.getArgs();
        LOGGER.info("start to execute , method = " + method.getName() + " , args = " + JSON.toJSONString(args));

        Class returnType = method.getReturnType();

        // 校验参数
        for (Object parameter : args) {
            try {
                BeanValidator.validateObject(parameter);
            } catch (ValidationException e) {
                printLog(stopWatch, method, args, "failed to validate", null, e);
                return getFailedResponse(returnType, e);
            }
        }

        try {
            // 执行目标方法
            Object response = pjp.proceed();
            enrichObject(response);
            printLog(stopWatch, method, args, "end to execute", response, null);
            return response;
        } catch (Throwable throwable) {
            // 处理异常
            printLog(stopWatch, method, args, "failed to execute", null, throwable);
            return getFailedResponse(returnType, throwable);
        }
    }

    /**
     * 打印日志信息
     *
     * @param stopWatch 耗时
     * @param method    方法
     * @param args      参数
     * @param action    动作
     * @param response  响应
     * @param throwable 异常
     */
    private void printLog(StopWatch stopWatch, Method method, Object[] args, String action, Object response,
                          Throwable throwable) {
        try {
            LOGGER.info(getInfoMessage(action, stopWatch, method, args, response, throwable), throwable);
        } catch (Exception e1) {
            LOGGER.error("log failed", e1);
        }
    }

    /**
     * 拼接日志信息
     *
     * @param action    行为描述
     * @param stopWatch 计时器
     * @param method    方法
     * @param args      参数
     * @param response  响应
     * @param exception 异常
     * @return 拼接后的日志信息
     */
    private String getInfoMessage(String action, StopWatch stopWatch, Method method, Object[] args, Object response,
                                  Throwable exception) {

        StringBuilder stringBuilder = new StringBuilder(action);
        stringBuilder.append(" ,method = ").append(method.getName());
        stringBuilder.append(" ,cost = ").append(stopWatch.getTime()).append(" ms");

        if (response instanceof BaseResponse) {
            stringBuilder.append(" ,success = ").append(((BaseResponse) response).getSuccess());
        }

        if (exception != null) {
            stringBuilder.append(" ,success = false");
        }

        stringBuilder.append(" ,args = ").append(JSON.toJSONString(Arrays.toString(args)));

        if (response != null) {
            stringBuilder.append(" ,resp = ").append(JSON.toJSONString(response));
        }

        if (exception != null) {
            stringBuilder.append(" ,exception = ").append(exception.getMessage());
        }

        if (response instanceof BaseResponse && !((BaseResponse) response).getSuccess()) {
            stringBuilder.append(" , execute_failed");
        }

        return stringBuilder.toString();
    }

    /**
     * 补全返回的响应对象
     *
     * @param response 响应对象
     */
    private void enrichObject(Object response) {
        if (response instanceof BaseResponse) {
            BaseResponse baseResponse = (BaseResponse) response;
            if (baseResponse.getSuccess()) {
                if (StringUtils.isEmpty(baseResponse.getResponseCode())) {
                    baseResponse.setResponseCode(ResponseCode.SUCCESS.name());
                }
            } else {
                if (StringUtils.isEmpty(baseResponse.getResponseCode())) {
                    baseResponse.setResponseCode(ResponseCode.BIZ_ERROR.name());
                }
            }
        }
    }

    /**
     * 获取失败的响应对象
     *
     * @param returnType 返回类型
     * @param throwable  异常
     * @return 失败响应
     * @throws NoSuchMethodException 方法不存在异常
     * @throws IllegalAccessException 访问异常
     * @throws InvocationTargetException 反射调用异常
     * @throws InstantiationException 实例化异常
     */
    private Object getFailedResponse(Class returnType, Throwable throwable)
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        if (returnType.getDeclaredConstructor().newInstance() instanceof BaseResponse) {
            BaseResponse response = (BaseResponse) returnType.getDeclaredConstructor().newInstance();
            response.setSuccess(false);
            if (throwable instanceof BizException) {
                response.setResponseMessage(((BizException) throwable).getErrorCode().getMessage());
                response.setResponseCode(((BizException) throwable).getErrorCode().getCode());
            } else if (throwable instanceof SystemException) {
                response.setResponseMessage(((SystemException) throwable).getErrorCode().getMessage());
                response.setResponseCode(((SystemException) throwable).getErrorCode().getCode());
            } else {
                response.setResponseMessage(throwable.toString());
                response.setResponseCode(ResponseCode.BIZ_ERROR.name());
            }
            return response;
        }

        LOGGER.error("failed to getFailedResponse , returnType (" + returnType + ") is not instanceof BaseResponse");
        return null;
    }
}
